import asyncio
import json
import os
from urllib.parse import unquote, urlencode
import aiohttp
from concurrent.futures import ThreadPoolExecutor

from loguru import logger


def jd(obj):
    return json.dumps(obj, ensure_ascii=False)


class SP:

    @staticmethod
    def read_file(file):
        with open(file, 'r', encoding='utf-8') as f:
            content = f.read()
            return content

    @staticmethod
    def trans_json_text_to_json(text_file_path):
        with open(text_file_path, 'r', encoding='utf8') as f:
            json_str = f.read()
        json_obj = json.loads(json_str)
        return json_obj

    @staticmethod
    def save_text(file=None, text=None, mode='w'):
        file_path = os.path.abspath(file)
        file_dir_path = os.path.dirname(file_path)
        if not os.path.exists(file_dir_path):
            os.makedirs(file_dir_path)
        with open(file, mode=mode, encoding='utf8') as f:
            f.write(text)


angle_num = 8
ap = int(360 / angle_num)
dl_failed_file = 'dl_failed.txt'


# 定义地图图片下载器类
class MapImageDownloader:
    pictures_dir = 'Map_imgs'
    google_map_api = 'https://maps.googleapis.com/maps/api/streetview'
    key = 'AIzaSyBlUJr1IlMi7AtbvJLkNG9r1m6awY8vguY'
    zbd_file = 'Taipei_0_150m.json'  # 坐标点文件
    req_times_limit = 25000
    circle_angle = 360
    distence = '150m'
    angles = [str(int(i * ap)) for i in range(angle_num)]
    dl_succeed = 0
    dl_failed = 0

    def __init__(self):
        self.coordinates = self.get_coordinates()[:4]

    def get_coordinates(self):
        data = SP.trans_json_text_to_json(self.zbd_file)['data']
        points = [i[2] for i in data]
        return points

    async def download_pic(self, session, lat, lon, heading):
        params = {
            'size': '640x640',
            'location': f'{lat},{lon}',
            'heading': f'{heading}',
            'key': self.key
        }
        query_string = urlencode(params)
        img_url = f'{self.google_map_api}?{query_string}'
        pic_path = os.path.join(
            self.pictures_dir,
            self.distence,
            f"{lat}_{lon}",
            f"{heading}.jpeg"
        )
        pic_dir = os.path.dirname(pic_path)
        if not os.path.exists(pic_dir):
            os.makedirs(pic_dir)
        downloaded = False
        for i in range(3):
            downloaded = await self.download(session, img_url, pic_path)
            if downloaded:
                break
        if not downloaded:
            dl_failed_str = jd(dict(
                img_url=img_url,
                pic_path=pic_path
            ))
            with open(dl_failed_file, 'a', encoding='utf-8') as f:
                f.write(dl_failed_str + '\n')
            self.dl_failed += 1
        else:
            self.dl_succeed += 1

    async def download(self, session, url, pic_path):
        file_name = os.path.basename(pic_path)
        try:
            async with session.get(
                    url,
                    stream=True,
                    verify=False,
                    timeout=10
            ) as r:
                with open(pic_path, 'wb') as f:
                    f.write(r.content)
                logger.success(f'{file_name} 下载完成')
                return True
        except Exception as e:
            logger.warning(f'url: {unquote(url)} 下载报错：{e}')

    async def download_image(self, session, url, filename):
        async with session.get(url) as response:
            with open(filename, 'wb') as file:
                while True:
                    chunk = await response.content.read(1024)
                    if not chunk:
                        break
                    file.write(chunk)

    async def download_images_for_coordinate(self, session, coordinate):
        tasks = []
        lat, lon = coordinate
        for heading in self.angles:
            task = self.download_pic(session, lat, lon, heading=heading)
            tasks.append(task)
        await asyncio.gather(*tasks)

    async def run(self):
        executor = ThreadPoolExecutor(max_workers=4)
        async with aiohttp.ClientSession() as session:
            loop = asyncio.get_event_loop()
            download_tasks = []
            for coordinate in self.coordinates:
                task = loop.run_in_executor(
                    executor,
                    self.download_images_for_coordinate,
                    session,
                    coordinate
                )
                download_tasks.append(task)

            await asyncio.gather(*download_tasks)


# 主函数
def main():
    downloader = MapImageDownloader()
    asyncio.run(downloader.run())


if __name__ == '__main__':
    main()
